import React, {PropTypes} from 'react';
import ReactDOM from "react-dom";
import classNames from 'classnames';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import Animate from "./Animate";
import loopAllChildren from "../src/utils/loopAllChildren"
function noop() {
}
export const Tree = React.createClass({
	propTypes: {
		prefixCls: PropTypes.string,
		children: PropTypes.any,
		showLine: PropTypes.bool,
		showIcon: PropTypes.bool,
		selectable: PropTypes.bool,
		multiple: PropTypes.bool,
		checkable: PropTypes.oneOfType([
			PropTypes.bool,
			PropTypes.node,
		]),
		_treeNodesStates: PropTypes.object,
		checkStrictly: PropTypes.bool,
		draggable: PropTypes.bool,
		autoExpandParent: PropTypes.bool,
		defaultExpandAll: PropTypes.bool,
		defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string),
		expandedKeys: PropTypes.arrayOf(PropTypes.string),
		defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string),
		checkedKeys: PropTypes.oneOfType([
			PropTypes.arrayOf(PropTypes.string),
			PropTypes.object,
		]),
		defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
		selectedKeys: PropTypes.arrayOf(PropTypes.string),
		onExpand: PropTypes.func,
		onCheck: PropTypes.func,
		onSelect: PropTypes.func,
		loadData: PropTypes.func,
		onMouseEnter: PropTypes.func,
		onMouseLeave: PropTypes.func,
		onRightClick: PropTypes.func,
		onDragStart: PropTypes.func,
		onDragEnter: PropTypes.func,
		onDragOver: PropTypes.func,
		onDragLeave: PropTypes.func,
		onDrop: PropTypes.func,
		filterTreeNode: PropTypes.func,
		openTransitionName: PropTypes.string,
		openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
	},
    mixins: [PureRenderMixin],      
    getDefaultProps() {
        return {
            prefixCls: 'rc-tree',
            showLine: false,
            showIcon: true,
            selectable: true,
            multiple: false,
            checkable: true,
            showChecked: false,
            draggable: false,                    
            defaultCheckedKeys: [],
            defaultSelectedKeys: [],
            onExpand: noop,
            onCheck: noop,
            onSelect: noop,
            onDragStart: noop,
            onDragEnter: noop,
            onDragOver: noop,
            onDragLeave: noop,
            onDrop: noop,
        };
    },
    checkedNodes: [],
	getInitialState: function() {           
		return {	
            expandedKeys: [],		
			checkedKeys: [],
			selectedKeys: [],
			dragNodesKeys: '',
			dragOverNodeKey: '',
			dropNodeKey: '',
			contextmenuKeys: [],
			checkedKeysChange : true,
		};
  	},
  	componentWillReceiveProps: function(nextProps) {	  
	    const checkedKeys = this.getDefaultCheckedKeys(nextProps, true);
	    const selectedKeys = this.getDefaultSelectedKeys(nextProps, true);
	    const st = {};
	    if (expandedKeys) {
	      st.expandedKeys = expandedKeys;
	    }
	    if (checkedKeys) {
	      if (nextProps.checkedKeys === this.props.checkedKeys) {
	        this.checkedKeysChange = false;
	      } else {
	        this.checkedKeysChange = true;
	      }
	      st.checkedKeys = checkedKeys;
	    }
	    if (selectedKeys) {
	      st.selectedKeys = selectedKeys;
	    }
	    this.setState(st);
  },
  onDragStart: function(e, treeNode) {
    this.dragNode = treeNode;
    this.dragNodesKeys = this.getDragNodes(treeNode);
    const st = {
      dragNodesKeys: this.dragNodesKeys,
    };
    const expandedKeys = this.getExpandedKeys(treeNode, false);
    if (expandedKeys) {
      // Controlled expand, save and then reset
      this.getRawExpandedKeys();
      st.expandedKeys = expandedKeys;
    }
    this.setState(st);
    this.props.onDragStart({
      event: e,
      node: treeNode,
    });
  },
  onDragEnterGap: function(e, treeNode) {
    // console.log(e.pageY, getOffset(treeNode.refs.selectHandle), treeNode.props.eventKey);
    const offsetTop = getOffset(treeNode.refs.selectHandle).top;
    const offsetHeight = treeNode.refs.selectHandle.offsetHeight;
    const pageY = e.pageY;
    const gapHeight = 2;
    if (pageY > offsetTop + offsetHeight - gapHeight) {
      this.dropPosition = 1;
      return 1;
    }
    if (pageY < offsetTop + gapHeight) {
      this.dropPosition = -1;
      return -1;
    }
    this.dropPosition = 0;
    return 0;
  },

    onDragEnter: function(e, treeNode) {
        const enterGap = this.onDragEnterGap(e, treeNode);
        if (this.dragNode.props.eventKey === treeNode.props.eventKey && enterGap === 0) {
            this.setState({
                dragOverNodeKey: '',
            });
            return;
        }
        const st = {
            dragOverNodeKey: treeNode.props.eventKey,
        };
        const expandedKeys = this.getExpandedKeys(treeNode, true);
        if (expandedKeys) {
            this.getRawExpandedKeys();
            st.expandedKeys = expandedKeys;
        }
        this.setState(st);
        this.props.onDragEnter({
            event: e,
            node: treeNode,
            expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys],
        });
    },
    onDragOver: function(e, treeNode) {
        this.props.onDragOver({
            event: e,
            node: treeNode
        });
    },

    onDragLeave: function(e, treeNode) {
        this.props.onDragLeave({
            event: e,
            node: treeNode
        });
    },
    onDrop: function(e, treeNode) {
        const key = treeNode.props.eventKey;
        this.setState({
            dragOverNodeKey: '',
            dropNodeKey: key,
        });
        if (this.dragNodesKeys.indexOf(key) > -1) {
            if (console.warn) {
                console.warn('can not drop to dragNode(include it\'s children node)');
            }
            return false;
        }

        const posArr = treeNode.props.pos.split('-');
        const res = {
            event: e,
            node: treeNode,
            dragNode: this.dragNode,
            dragNodesKeys: [...this.dragNodesKeys],
            dropPosition: this.dropPosition + Number(posArr[posArr.length - 1]),
        };
        if (this.dropPosition !== 0) {
            res.dropToGap = true;
        }
        if ('expandedKeys' in this.props) {
            res.rawExpandedKeys = [...this._rawExpandedKeys] || [...this.state.expandedKeys];
        }
        this.props.onDrop(res);
    },      

    onSelect: function(treeNode) {
        const props = this.props;
        const selectedKeys = [...this.state.selectedKeys];
        const eventKey = treeNode.props.eventKey;
        const index = selectedKeys.indexOf(eventKey);
        let selected;
        if (index !== -1) {
            selected = false;
            selectedKeys.splice(index, 1);
        } else {
            selected = true;
            if (!props.multiple) {
                selectedKeys.length = 0;
            }
            selectedKeys.push(eventKey);
        }
        const selectedNodes = [];
        if (selectedKeys.length) {
            loopAllChildren(this.props.children, (item) => {
                if (selectedKeys.indexOf(item.key) !== -1) {
                    selectedNodes.push(item);
                }
            });
        }
        const newSt = {
            event: 'select',
            node: treeNode,
            selected,
            selectedNodes,
        };
        if (!('selectedKeys' in this.props)) {
            this.setState({
                selectedKeys,
            });
        }
        props.onSelect(selectedKeys, newSt);
    },

    onMouseEnter: function(e, treeNode) {
        this.props.onMouseEnter({
            event: e,
            node: treeNode
        });
    },

    onMouseLeave: function(e, treeNode) {
        this.props.onMouseLeave({
            event: e,
            node: treeNode
        });
    },
    onContextMenu: function(e, treeNode) {
        const selectedKeys = [...this.state.selectedKeys];
        const eventKey = treeNode.props.eventKey;
        if (this.contextmenuKeys.indexOf(eventKey) === -1) {
            this.contextmenuKeys.push(eventKey);
        }
        this.contextmenuKeys.forEach((key) => {
            const index = selectedKeys.indexOf(key);
            if (index !== -1) {
                selectedKeys.splice(index, 1);
            }
        });
        if (selectedKeys.indexOf(eventKey) === -1) {
            selectedKeys.push(eventKey);
        }
        this.setState({
            selectedKeys,
        });
        this.props.onRightClick({
            event: e,
            node: treeNode
        });
    },

    // all keyboard events callbacks run from here at first
    onKeyDown: function(e) {
        e.preventDefault();
    },

    getOpenTransitionName: function() {
        const props = this.props;
        let transitionName = props.openTransitionName;
        const animationName = props.openAnimation;
        if (!transitionName && typeof animationName === 'string') {
            transitionName = `${props.prefixCls}-open-${animationName}`;
        }
        return transitionName;
    },

    getDragNodes: function(treeNode) {
        const dragNodesKeys = [];
        const tPArr = treeNode.props.pos.split('-');
        loopAllChildren(this.props.children, (item, index, pos, newKey) => {
            const pArr = pos.split('-');
            if (treeNode.props.pos === pos || tPArr.length < pArr.length && isInclude(tPArr, pArr)) {
                dragNodesKeys.push(newKey);
            }
        });
        return dragNodesKeys;
    },

    getExpandedKeys: function(treeNode, expand) {
        const key = treeNode.props.eventKey;
        const expandedKeys = this.state.expandedKeys;
        const expandedIndex = expandedKeys.indexOf(key);
        let exKeys;
        if (expandedIndex > -1 && !expand) {
            exKeys = [...expandedKeys];
            exKeys.splice(expandedIndex, 1);
            return exKeys;
        }
        if (expand && expandedKeys.indexOf(key) === -1) {
            return expandedKeys.concat([key]);
        }
    },
    filterTreeNode: function(treeNode) {
        const filterTreeNode = this.props.filterTreeNode;
        if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) {
            return false;
        }
        return filterTreeNode.call(this, treeNode);
    },

    checkedNode: function(nodeKey){          
        var checkedEvent = new CustomEvent("checkedNode", {
            detail: {nodeKey}
        });
        ReactDOM.findDOMNode(this).dispatchEvent(checkedEvent);
    },
    expandedNode: function(nodeKey){
        var checkedEvent = new CustomEvent("expandedNode", {
            detail: {nodeKey}
        });
        ReactDOM.findDOMNode(this).dispatchEvent(checkedEvent);
    },
    addCheckedNode: function(treeNode){            
        this.checkedNodes.push(treeNode);           
    },
    removeCheckedNode: function(treeNode){        
        const index = this.checkedNodes.indexOf(treeNode);
        if (index !== -1) {
            this.checkedNodes.splice(index, 1);
        }           
    },
    renderTreeNode: function(child, index, level = 0, parent) {  
        const pos = `${level}-${index}`;
        const key = child.key || pos;
        const state = this.state;
        const props = this.props;      
        const cloneProps = {           
            ref: 'treeNode-' + key,
            root: this,
            parent: parent,
            eventKey: key,            
            selectable: props.selectable,          
            onMouseEnter: props.onMouseEnter,
            onMouseLeave: props.onMouseLeave,
            onRightClick: props.onRightClick,
            onCheck: props.onCheck,
            onExpand: props.onExpand,            
            prefixCls: props.prefixCls,
            showLine: props.showLine,
            showIcon: props.showIcon,            
            draggable: props.draggable,
            showChecked: props.multiple || props.showChecked,
            checkable: props.checkable,
            multiple: props.multiple,
            iconClass: child.props.iconClass || props.iconClass,
            dragOver: state.dragOverNodeKey === key && this.dropPosition === 0,
            dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1,
            dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1,         
            openTransitionName: this.getOpenTransitionName(),         
        };      
        
        return React.cloneElement(child, cloneProps);
    },
	render: function() {
		const props = this.props;       
		const domProps = {
			className: classNames("tree", props.className),
			role: 'tree-node',
		};
		if (props.focusable) {
			domProps.tabIndex = '0';
			domProps.onKeyDown = this.onKeyDown;
		}
		// console.log(this.state.expandedKeys, this._rawExpandedKeys, props.children);
		return ( 
            <div {...domProps} unselectable ref ="tree"> {            
				React.Children.map(props.children, this.renderTreeNode, this)
			} </div>
		);
	}
});

export const TreeNode = React.createClass({
    getDefaultProps() {
        return {
            onExpand: noop,
            unSelectable: false,
            multiSelectable: false,           
        }
    },
    mixins: [PureRenderMixin],    
    getInitialState: function() {      
        return {    
            expanded: false,               
            checked: false,
        };
    },    
    onCheckedNode: function(event){     
        if (event.detail.nodeKey == this.props.eventKey){            
            this.check(true);
        }
        event.stopPropagation();
    },
    onExpandNode: function(event){
        if (event.detail.nodeKey == this.props.eventKey){            
            this.expand(true);
        }
        event.stopPropagation();
    },
    componentDidMount: function() {  
        ReactDOM.findDOMNode(this.props.root).addEventListener("checkedNode", this.onCheckedNode);
        ReactDOM.findDOMNode(this.props.root).addEventListener("expandedNode", this.onExpandNode);
    },
    componentWillUnmount: function(){
        ReactDOM.findDOMNode(this.props.root).removeEventListener("checkedNode", this.onCheckedNode);
        ReactDOM.findDOMNode(this.props.root).removeEventListener("expandedNode", this.onExpandNode);
    },
    onCheck: function(event) {             
        this.props.disabled || this.check(!this.state.checked);  
        event.stopPropagation();     
    },
    onSelect: function() {
        this.props.root.onSelect(this);
    },
    onMouseEnter: function(e) {
        e.preventDefault();
        this.props.root.onMouseEnter(e, this);
    },
    onMouseLeave: function(e) {
        e.preventDefault();
        this.props.root.onMouseLeave(e, this);
    },

    onContextMenu: function(e) {
        e.preventDefault();
        this.props.root.onContextMenu(e, this);
    },

    onDragStart: function(e) {
        // console.log('dragstart', this.props.eventKey, e);
        // e.preventDefault();
        e.stopPropagation();
        this.setState({
            dragNodeHighlight: true,
        });
        this.props.root.onDragStart(e, this);
        try {
            // ie throw error
            e.dataTransfer.setData('text/plain', 'firefox-need-it');
        } finally {
            // empty
        }
    },
    onDragEnter: function(e) {
        // console.log('dragenter', this.props.eventKey, e);
        e.preventDefault();
        e.stopPropagation();
        this.props.root.onDragEnter(e, this);
    },

    onDragOver: function(e) {       
        e.preventDefault();
        e.stopPropagation();
        this.props.root.onDragOver(e, this);
        return false;
    },

    onDragLeave: function(e) {      
        e.stopPropagation();
        this.props.root.onDragLeave(e, this);
    },

    onDrop: function(e) {
        e.preventDefault();
        e.stopPropagation();
        this.setState({
            dragNodeHighlight: false,
        });
        this.props.root.onDrop(e, this);
    },

    onExpand: function(event) {        
        this.expand(!this.state.expanded);   
        event.stopPropagation();   
    },
    handleClick: function(event){
        if (!this.props.multiple){
            this.expand(!this.state.expanded);
        }
        this.props.disabled || this.check(true);
    },
    expand: function(expanded){
        this.props.onExpand();
        this.setState({expanded})  

        if (expanded && this.props.parent){        
            this.props.parent.expand(true);
        }
    },
    check: function(checked){
        if (!this.props.multiple){
            var oldCheckedNode = this.props.root.oldCheckedNode;
            if (checked) {
                if (oldCheckedNode && oldCheckedNode !== this) {
                    oldCheckedNode.check(false);
                }
                this.props.root.oldCheckedNode = this;
            } 
            else {
                if (oldCheckedNode && oldCheckedNode === this) {
                    this.props.root.oldCheckedNode = null;
                }
            }
        }
        if (checked){
            this.props.root.addCheckedNode(this);
        }
        else{
            this.props.root.removeCheckedNode(this);
        }     

        if (this.props.parent){
            this.props.parent.expand(true);
        }
        this.setState({checked});
        this.props.onCheck();
    },
  // keyboard event support
    onKeyDown: function(e) {
        e.preventDefault();
    },
    renderSwitcher: function(props, expandedState) {     
        const switcherCls = {
            'fa-folder' : !expandedState,
            'fa-folder-open': expandedState,           
        }
        return (<i className={classNames(switcherCls, 'fa')} onClick={this.onExpand}></i>);
    },
    renderCheckbox: function(props, checkedState) {        
        if (!props.multiple && !props.showChecked){
            return null;
        }     
        const checkboxCls = {
            'fa-check' : checkedState,
            'fa-square-o' : !checkedState
        }
        //this.props.showCheckbox
        return (<i className={classNames(props.showChecked ? checkboxCls: {}, "fa tree-check")} onClick={this.onCheck} ></i>);
    },
    renderIcon: function(props){
        return (<i style={{marginLeft: '4px'}} className={props.iconClass}/>)
    },
    renderChildren: function(props, expanded) {
        const children = props.children;
        let newChildren = null;
        let allTreeNode = false;        
        if (Array.isArray(children)) {
            allTreeNode = children.every((item) => {
                return item.type === TreeNode;
            });
        }
        else if (children){
            allTreeNode = children.type === TreeNode
        }

        if (allTreeNode) {           
            const animProps = {};  
            const childrenStyle = {
                display : !expanded ? 'none': ''
            }             
            newChildren = ( 
                <Animate {...animProps} showProp = "expanded">
                    <div className={'tree-folder-content'} style={childrenStyle}>  
                        {
                            React.Children.map(children, (item, index) => {                                
                                return props.root.renderTreeNode(item, index, props.pos, this);
                            }, props.root)
                        }
                    </div>
                </Animate>
            );
        }
        return newChildren;
    },   
    render: function() {   
        const props = this.props;
        const prefixCls = props.prefixCls;            

        let hasChildren = true;       
        let newChildren = this.renderChildren(props, this.state.expanded);       
        if (!newChildren) {
            hasChildren = false;
        }

        // 如果默认不展开，不渲染进dom，在大量数据下，能使性能有很大提升！
        // if (!this.state.expanded) {
        //     newChildren = null;
        // }       

        const liProps = {};      
        
        const headerCls = {
            'tree-selected' : this.state.checked,
            "disabled" : this.props.disabled
        }

        const headerProps = {};

        if (props.draggable){
            headerProps.draggable = true;
            headerProps.onDragStart = this.onDragStart;
        } 
        if (hasChildren){
            return (
                <div className={'tree-folder'}>
                    <div onClick={this.handleClick} ref='header'
                        className={classNames(headerCls, "tree-folder-header")}
                        {...headerProps}>
                        {this.renderSwitcher(props, this.state.expanded)}                      
                        {props.checkable ? this.renderCheckbox(props, this.state.checked) : null}                       
                        <div  className="tree-folder-name">
                            {props.title}
                        </div>                    
                    </div>
                    {newChildren}
                </div>
            );
        }
        else{
            return (
                <div ref="item" onClick={this.handleClick} 
                    className={classNames(headerCls, 'tree-item')}
                    {...headerProps}>                   
                    {props.checkable ? this.renderCheckbox(props, this.state.checked) : null}                    
                    <div className="tree-item-name">
                        {this.renderIcon(props)}
                        {props.title}
                    </div>      
                </div>
            );
        }       
      }
})